package com.sts.panel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.formdev.flatlaf.FlatClientProperties;
import com.github.rhwood.jsplitbutton.JSplitButton;
import com.sts.Main;
import com.sts.common.utils.*;
import com.sts.handler.FileChooserTransferHandler;
import com.sts.jdbc.JdbcOperation;
import com.sts.jdbc.model.JdbcConnectionInfo;
import com.sts.listener.ComboBoxKeyListener;
import com.sts.sink.impl.JdbcSinkImpl;
import com.sts.sink.model.*;
import com.sts.source.model.*;
import com.sts.task.SinkTypeEnum;
import com.sts.task.StsTask;
import com.sts.transform.StsTransform;
import com.sts.transform.impl.RuleGenerateTransformImpl;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.miginfocom.swing.MigLayout;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.jdesktop.swingx.JXBusyLabel;

import javax.sql.DataSource;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.List;

/**
 * @author lsl
 * @since 2025年07月16日 20:32
 */
@Getter
@Slf4j
public class StsTaskPanel extends JPanel {
    private final String panelName;
    public final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
    private final TaskConfigPanel taskConfigPanel;
    private final LogPanel logPanel;
    private StsTask stsTask;

    public StsTaskPanel() {
        panelName = IdUtil.fastSimpleUUID();
        setLayout(new BorderLayout());
        add(splitPane, BorderLayout.CENTER);

        taskConfigPanel = new TaskConfigPanel();
        splitPane.setLeftComponent(taskConfigPanel);
        splitPane.setDividerSize(3);
        logPanel = new LogPanel();
        splitPane.setRightComponent(logPanel);

        splitPane.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                if (splitPane.getHeight() > 50) {
                    splitPane.setDividerLocation(0.7);
                    splitPane.removeComponentListener(this);
                }
            }
        });
    }

    @Getter
    public class TaskConfigPanel extends JPanel {
        private final SourcePanel sourcePanel;
        private final SinkPanel sinkPanel;
        private final TransformPanel transformPanel;
        private final RunButtonPanel runButtonPanel;

        public TaskConfigPanel() {
            setLayout(new MigLayout("insets 10 10 10 10, fill", "[] []", "[] []"));
            sourcePanel = new SourcePanel();
            JScrollPane sourceScrollPane = new JScrollPane(sourcePanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            sourceScrollPane.setBorder(null);
            add(sourceScrollPane, "cell 0 0, align center, grow");
            sinkPanel = new SinkPanel();
            JScrollPane sinkPanelScrollPane = new JScrollPane(sinkPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            sinkPanelScrollPane.setBorder(null);
            add(sinkPanelScrollPane, "cell 1 0, align center, grow");
            transformPanel = new TransformPanel();
            add(transformPanel, "cell 0 1, span, growx");
            runButtonPanel = new RunButtonPanel();
            add(runButtonPanel, "cell 0 2, span, growx");
        }
    }

    @Getter
    public class SourcePanel extends JPanel {

        private JComboBox<String> sourceComboBox;
        private JTextField sourceTextField;
        private ComboBoxKeyListener sourceComboBoxKeyListener;

        private JLabel filePathLabel;
        private JTextField filePathTextField;
        private JButton chooseFileButton;

        private JLabel sqlLabel;
        private RSyntaxTextArea sqlTextArea;
        private RTextScrollPane sqlTextScrollPane;

        private JButton sourceConfigButton;

        public SourcePanel() {
            MigLayout migLayout = new MigLayout("insets 10 10 10 0, fillx", "[]", "[] [] []");
            setLayout(migLayout);
            Font defaultFont = Singleton.get(ConfigFileUtil.class).getDefaultFont();
            Font titleFont = new Font(defaultFont.getFontName(), Font.BOLD, defaultFont.getSize() + 2);
            this.setBorder(new TitledBorder(null, "Source", TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, titleFont, null));
            initUI();
            initUIData();
            initAction();
        }

        private void initUI() {
            // Source Label
            JLabel sourceLabel = new JLabel("Source:");
            add(sourceLabel, "cell 0 0, wmin 60, wmax 60, gapy 10");

            sourceComboBox = new JComboBox<>();
            sourceComboBox.setPrototypeDisplayValue("xxxxxxxx");
            sourceComboBox.setEditable(true);
            sourceTextField = (JTextField) sourceComboBox.getEditor().getEditorComponent();
            sourceTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            sourceComboBoxKeyListener = new ComboBoxKeyListener(sourceComboBox);
            sourceTextField.addKeyListener(sourceComboBoxKeyListener);
            add(sourceComboBox, "cell 0 0, growx, gapx 10");

            DefaultComboBoxModel<String> sourceComboBoxModel = (DefaultComboBoxModel<String>) sourceComboBox.getModel();
            sourceComboBoxModel.addElement("File");

            // 文件
            filePathLabel = new JLabel("文件:");
            add(filePathLabel, "cell 0 1, wmin 60, wmax 60, gapy 10");
            // 文件输入框
            filePathTextField = new JTextField();
            filePathTextField.setColumns(1);
            filePathTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            chooseFileButton = new JButton(Singleton.get(ImageUtil.class).getChooseFileIcon());
            filePathTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, chooseFileButton);
            add(filePathTextField, "cell 0 1, growx, gapx 10");
            filePathTextField.setTransferHandler(new FileChooserTransferHandler(filePathTextField));

            sqlLabel = new JLabel("Sql:");

            sqlTextArea = new RSyntaxTextArea();
            sqlTextArea.setColumns(1);
            sqlTextArea.setRows(9);
            sqlTextArea.setFont(Singleton.get(ConfigFileUtil.class).getDefaultFont());
            sqlTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_SQL);
            sqlTextScrollPane = new RTextScrollPane(sqlTextArea);
            sqlTextScrollPane.setLineNumbersEnabled(true);

            sourceConfigButton = new JButton("配置");
            sourceConfigButton.setEnabled(false);
            add(sourceConfigButton, "cell 0 2, alignx left, gapy 10");
        }

        private void initUIData() {
            Set<String> jdbcNameSet = ConfigFileUtil.CONNECTION_INFO_MAP.keySet();
            DefaultComboBoxModel<String> sourceComboBoxModel = (DefaultComboBoxModel<String>) sourceComboBox.getModel();
            sourceComboBoxModel.addAll(jdbcNameSet);
            List<String> list = sourceComboBoxKeyListener.getList();
            for (int i = 0; i < sourceComboBoxModel.getSize(); i++) {
                list.add(sourceComboBoxModel.getElementAt(i));
            }
        }

        private void initAction() {
            chooseFileButton.addActionListener(_ -> {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileFilter(new FileFilter() {
                    @Override
                    public boolean accept(File f) {
                        String name = f.getName();
                        return StrUtil.endWithIgnoreCase(name, ".csv") || StrUtil.endWithIgnoreCase(name, ".xlsx") || StrUtil.endWithIgnoreCase(name, ".txt");
                    }

                    @Override
                    public String getDescription() {
                        return "csv/excel/txt";
                    }
                });
                fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
                fileChooser.setMultiSelectionEnabled(false);
                fileChooser.setCurrentDirectory(FileSystemView.getFileSystemView().getHomeDirectory());
                int result = fileChooser.showOpenDialog(null);
                if (Objects.equals(result, JFileChooser.APPROVE_OPTION)) {
                    String absolutePath = fileChooser.getSelectedFile().getAbsolutePath();
                    StsTaskPanel.this.logPanel.getLogTextArea().append(StrUtil.format("source 已选择文件: {}\n", absolutePath));
                    filePathTextField.setText(absolutePath);
                }
            });

            sourceComboBox.addActionListener(_ -> {
                String selectItem = sourceComboBox.getItemAt(sourceComboBox.getSelectedIndex());
                if (StrUtil.equals(selectItem, "File")) {
                    this.remove(sqlLabel);
                    this.remove(sqlTextScrollPane);
                    add(filePathLabel, "cell 0 1, wmin 60, wmax 60, gapy 10");
                    add(filePathTextField, "cell 0 1, growx, gapx 10");
                    add(sourceConfigButton, "cell 0 2, alignx left, gapy 10");
                } else {
                    this.remove(filePathLabel);
                    this.remove(filePathTextField);
                    this.remove(sourceConfigButton);
                    add(sqlLabel, "cell 0 1, wmin 60, wmax 60, gapy 10");
                    add(sqlTextScrollPane, "cell 0 1, growx, gapx 10");
                    add(sourceConfigButton, "cell 0 2, alignx left, gapy 10");
                }
                SwingUtilities.invokeLater(() -> {
                    this.revalidate();
                    this.repaint();

                    Container parent = SourcePanel.this.getParent();
                    if (parent instanceof JViewport) {
                        Container scrollPane = parent.getParent();
                        Container taskPanel = scrollPane.getParent();
                        taskPanel.revalidate();
                        taskPanel.repaint();
                    }
                });
            });

            sourceTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_CLEAR_CALLBACK, (Runnable) () -> {
                sourceTextField.setText("");

                DefaultComboBoxModel<String> sourceComboBoxModel = new DefaultComboBoxModel<>(sourceComboBoxKeyListener.getList().toArray(new String[0]));
                ComboBoxKeyListener.setSuggestionModel(sourceComboBox, sourceComboBoxModel, "");
                sourceComboBox.hidePopup();
            });
        }

    }

    @Getter
    public class TransformPanel extends JPanel {

        private JCheckBox mockCheckBox;
        private JTextField symbolTextField;

        public TransformPanel() {
            MigLayout migLayout = new MigLayout("insets 10 10 10 10, fillx", "[]", "[]");
            setLayout(migLayout);
            Font defaultFont = Singleton.get(ConfigFileUtil.class).getDefaultFont();
            Font titleFont = new Font(defaultFont.getFontName(), Font.BOLD, defaultFont.getSize() + 2);
            this.setBorder(new TitledBorder(null, "Transform", TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, titleFont, null));
            initUI();
            initUIData();
            initAction();
        }

        private void initUI() {
            JLabel mockLabel = new JLabel("规则生成:");
            add(mockLabel, "cell 0 0, wmin 60, wmax 60");

            mockCheckBox = new JCheckBox();
            add(mockCheckBox, "cell 0 0");

            JLabel mockSymbol = new JLabel("符号:");
            add(mockSymbol, "cell 0 0, gapx 20");

            symbolTextField = new JTextField("$");
            symbolTextField.setEnabled(false);
            add(symbolTextField, "cell 0 0");

            JButton transformConfigButton = new JButton("配置");
            transformConfigButton.setEnabled(false);
            add(transformConfigButton, "cell 0 0, gapx 20");
        }

        private void initUIData() {

        }

        private void initAction() {
            mockCheckBox.addActionListener(_ -> symbolTextField.setEnabled(mockCheckBox.isSelected()));
        }
    }

    @Getter
    public class SinkPanel extends JPanel {

        private JComboBox<String> sinkComboBox;
        private JTextField sinkTextField;
        private ComboBoxKeyListener sinkComboBoxKeyListener;

        private JLabel filePathLabel;
        private JTextField filePathTextField;
        private JButton chooseFileButton;

        private JLabel schemaLabel;
        private JComboBox<String> schemaComboBox;
        private JTextField schemaTextField;
        private JButton schemaButton;
        private ComboBoxKeyListener schemaComboBoxKeyListener;

        private JLabel tableLabel;
        private JComboBox<String> tableComboBox;
        private JTextField tableTextField;
        private JButton tableButton;
        private ComboBoxKeyListener tableComboBoxKeyListener;

        private JButton sinkConfigButton;
        private JButton clearTableButton;

        private final SinkConfigDialog sinkConfigDialog = new SinkConfigDialog();

        public SinkPanel() {
            MigLayout migLayout = new MigLayout("insets 10 10 10 0, fillx", "[]", "[] [] [] [] [] [] []");
            setLayout(migLayout);
            Font defaultFont = Singleton.get(ConfigFileUtil.class).getDefaultFont();
            Font titleFont = new Font(defaultFont.getFontName(), Font.BOLD, defaultFont.getSize() + 2);
            this.setBorder(new TitledBorder(null, "Sink", TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, titleFont, null));
            initUI();
            initUIData();
            initAction();
        }

        private void initUI() {
            // Sink Label
            JLabel sinkLabel = new JLabel("Sink:");
            add(sinkLabel, "cell 0 0, wmin 60, wmax 60, gapy 10");

            sinkComboBox = new JComboBox<>();
            sinkComboBox.setPrototypeDisplayValue("xxxxxxxx");
            sinkComboBox.setEditable(true);
            sinkTextField = (JTextField) sinkComboBox.getEditor().getEditorComponent();
            sinkTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            sinkComboBoxKeyListener = new ComboBoxKeyListener(sinkComboBox);
            sinkTextField.addKeyListener(sinkComboBoxKeyListener);
            add(sinkComboBox, "cell 0 0, growx, gapx 10");

            DefaultComboBoxModel<String> sinkComboBoxModel = (DefaultComboBoxModel<String>) sinkComboBox.getModel();
            sinkComboBoxModel.addElement("File");

            // 文件
            filePathLabel = new JLabel("文件:");
            add(filePathLabel, "cell 0 1, wmin 60, wmax 60, gapy 10");
            // 文件输入框
            filePathTextField = new JTextField();
            filePathTextField.setColumns(1);
            filePathTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            chooseFileButton = new JButton(Singleton.get(ImageUtil.class).getChooseFileIcon());
            filePathTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, chooseFileButton);
            add(filePathTextField, "cell 0 1, growx, gapx 10");

            // 模式
            schemaLabel = new JLabel("模式:");
            // 模式下拉框
            schemaComboBox = new JComboBox<>();
            schemaComboBox.setEditable(true);
            schemaComboBox.setPrototypeDisplayValue("xxxxxxxx");
            schemaTextField = (JTextField) schemaComboBox.getEditor().getEditorComponent();
            schemaTextField.setColumns(3);
            schemaTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            schemaButton = new JButton(Singleton.get(ImageUtil.class).getChooseFileIcon());
            schemaTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, schemaButton);
            schemaComboBoxKeyListener = new ComboBoxKeyListener(schemaComboBox);
            schemaTextField.addKeyListener(schemaComboBoxKeyListener);

            // 表名
            tableLabel = new JLabel("表名:");
            // 表名下拉框
            tableComboBox = new JComboBox<>();
            tableComboBox.setEditable(true);
            tableComboBox.setPrototypeDisplayValue("xxxxxxxx");
            tableTextField = (JTextField) tableComboBox.getEditor().getEditorComponent();
            tableTextField.setColumns(3);
            tableTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            tableButton = new JButton(Singleton.get(ImageUtil.class).getChooseFileIcon());
            tableTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, tableButton);
            tableComboBoxKeyListener = new ComboBoxKeyListener(tableComboBox);
            tableTextField.addKeyListener(tableComboBoxKeyListener);

            sinkConfigButton = new JButton("配置");
            add(sinkConfigButton, "cell 0 2, alignx left, gapy 10");

            clearTableButton = new JButton("清空表");
        }

        private void initUIData() {
            Set<String> jdbcNameSet = ConfigFileUtil.CONNECTION_INFO_MAP.keySet();
            DefaultComboBoxModel<String> sinkComboBoxModel = (DefaultComboBoxModel<String>) sinkComboBox.getModel();
            sinkComboBoxModel.addAll(jdbcNameSet);
            List<String> list = sinkComboBoxKeyListener.getList();
            for (int i = 0; i < sinkComboBoxModel.getSize(); i++) {
                list.add(sinkComboBoxModel.getElementAt(i));
            }
        }

        private void initAction() {
            chooseFileButton.addActionListener(_ -> {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
                fileChooser.setMultiSelectionEnabled(false);
                fileChooser.setCurrentDirectory(FileSystemView.getFileSystemView().getHomeDirectory());

                fileChooser.setSelectedFile(new File("sink.csv"));

                int result = fileChooser.showSaveDialog(Main.rootFrame);
                if (Objects.equals(result, JFileChooser.APPROVE_OPTION)) {
                    String absolutePath = fileChooser.getSelectedFile().getAbsolutePath();
                    File newFile = new File(absolutePath);
                    if (newFile.exists()) {
                        // 存在
                        if (JOptionPane.showConfirmDialog(fileChooser, "文件已存在，是否要覆盖它?", "文件已存在", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
                            // 不同意覆盖
                            return;
                        }
                    }
                    // 文件不存在，可以直接保存
                    logPanel.getLogTextArea().append(StrUtil.format("sink 文件路径: {}\n", absolutePath));
                    filePathTextField.setText(absolutePath);
                }
            });

            sinkComboBox.addActionListener(_ -> {
                String selectItem = sinkComboBox.getItemAt(sinkComboBox.getSelectedIndex());
                if (StrUtil.equals(selectItem, "File")) {
                    this.remove(schemaLabel);
                    this.remove(schemaComboBox);
                    this.remove(tableLabel);
                    this.remove(tableComboBox);
                    this.remove(clearTableButton);

                    add(filePathLabel, "cell 0 1, wmin 60, wmax 60, gapy 10");
                    add(filePathTextField, "cell 0 1, growx, gapx 10");
                    add(sinkConfigButton, "cell 0 2, alignx left, gapy 10");
                } else {
                    this.remove(filePathLabel);
                    this.remove(filePathTextField);
                    this.remove(sinkConfigButton);

                    add(schemaLabel, "cell 0 1, wmin 60, wmax 60, gapy 10");
                    add(schemaComboBox, "cell 0 1, growx, gapx 10");

                    add(tableLabel, "cell 0 2, wmin 60, wmax 60, gapy 10");
                    add(tableComboBox, "cell 0 2, growx, gapx 10");
                    add(sinkConfigButton, "cell 0 3, alignx left, gapy 10");
                    add(clearTableButton, "cell 0 3");
                }

                SwingUtilities.invokeLater(() -> {
                    this.revalidate();
                    this.repaint();

                    Container parent = SinkPanel.this.getParent();
                    if (parent instanceof JViewport) {
                        Container scrollPane = parent.getParent();
                        Container taskPanel = scrollPane.getParent();
                        taskPanel.revalidate();
                        taskPanel.repaint();
                    }
                });
            });

            sinkTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_CLEAR_CALLBACK, (Runnable) () -> {
                sinkTextField.setText("");

                DefaultComboBoxModel<String> sinkComboBoxModel = new DefaultComboBoxModel<>(sinkComboBoxKeyListener.getList().toArray(new String[0]));
                ComboBoxKeyListener.setSuggestionModel(sinkComboBox, sinkComboBoxModel, "");
                sinkComboBox.hidePopup();
            });

            schemaTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_CLEAR_CALLBACK, (Runnable) () -> {
                schemaTextField.setText("");

                DefaultComboBoxModel<String> schemaComboBoxModel = new DefaultComboBoxModel<>(schemaComboBoxKeyListener.getList().toArray(new String[0]));
                ComboBoxKeyListener.setSuggestionModel(schemaComboBox, schemaComboBoxModel, "");
                schemaComboBox.hidePopup();
            });

            tableTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_CLEAR_CALLBACK, (Runnable) () -> {
                tableTextField.setText("");

                DefaultComboBoxModel<String> tableComboBoxModel = new DefaultComboBoxModel<>(tableComboBoxKeyListener.getList().toArray(new String[0]));
                ComboBoxKeyListener.setSuggestionModel(tableComboBox, tableComboBoxModel, "");
                tableComboBox.hidePopup();
            });

            schemaButton.addActionListener(_ -> CompletableFutureUtil.runAndThrowExceptionFrontPresent(() -> {
                schemaButton.setEnabled(false);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setVisible(true);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setBusy(true);

                String sinkKey = sinkTextField.getText();
                Assert.isTrue(StrUtil.isNotBlank(sinkKey), "请先选择 Sink");
                Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                Assert.isTrue(connectionInfoMap.containsKey(sinkKey), "根据 key 找不到 Sink : " + sinkKey);

                logPanel.getLogTextArea().append(StrUtil.format(DateUtil.now() + " - " + "正在获取 {}  下所有的模式......\n", sinkKey));
                JdbcConnectionInfo jdbcConnectionInfo = connectionInfoMap.get(sinkKey);
                Pair<Class<? extends JdbcSinkImpl>, Class<? extends JdbcOperation>> pair = DbPairUtil.getPair(jdbcConnectionInfo.getDbType());
                try {
                    JdbcOperation jdbcOperation = pair.getValue().getDeclaredConstructor().newInstance();
                    List<String> allSchema = jdbcOperation.getAllSchema(JdbcConnectionUtil.getConnection(jdbcConnectionInfo));

                    if (CollectionUtil.isNotEmpty(allSchema)) {
                        SwingUtilities.invokeLater(() -> {
                            DefaultComboBoxModel<String> schemaComboBoxModel = (DefaultComboBoxModel<String>) schemaComboBox.getModel();
                            schemaComboBoxModel.removeAllElements();
                            schemaComboBoxModel.addAll(allSchema);
                            schemaComboBox.setSelectedIndex(-1);
                        });
                        List<String> schemaValueList = schemaComboBoxKeyListener.getList();
                        schemaValueList.clear();
                        schemaValueList.addAll(allSchema);
                        logPanel.getLogTextArea().append(StrUtil.format(DateUtil.now() + " - " + "已获取 {} 下所有的模式的数量为: {}\n", sinkKey, allSchema.size()));
                    }
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                         NoSuchMethodException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }, () -> {
                schemaButton.setEnabled(true);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setVisible(false);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setBusy(false);
            }, logPanel.getLogTextArea()));

            tableButton.addActionListener(_ -> CompletableFutureUtil.runAndThrowExceptionFrontPresent(() -> {
                tableButton.setEnabled(false);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setVisible(true);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setBusy(true);

                String sinkKey = sinkTextField.getText();
                Assert.isTrue(StrUtil.isNotBlank(sinkKey), "请先选择 Sink");
                Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                Assert.isTrue(connectionInfoMap.containsKey(sinkKey), "根据 key 找不到 Sink : " + sinkKey);

                String schema = schemaTextField.getText();
                Assert.isTrue(StrUtil.isNotBlank(schema), "请先选择模式");

                logPanel.getLogTextArea().append(StrUtil.format(DateUtil.now() + " - " + "正在获取 {}  下的 {} 下所有的表......\n", sinkKey, schema));

                JdbcConnectionInfo jdbcConnectionInfo = connectionInfoMap.get(sinkKey);
                Pair<Class<? extends JdbcSinkImpl>, Class<? extends JdbcOperation>> pair = DbPairUtil.getPair(jdbcConnectionInfo.getDbType());
                try {
                    JdbcOperation jdbcOperation = pair.getValue().getDeclaredConstructor().newInstance();
                    List<String> allTable = jdbcOperation.getAllTable(JdbcConnectionUtil.getConnection(jdbcConnectionInfo), schema);

                    if (CollectionUtil.isNotEmpty(allTable)) {
                        SwingUtilities.invokeLater(() -> {
                            DefaultComboBoxModel<String> tableComboBoxModel = (DefaultComboBoxModel<String>) tableComboBox.getModel();
                            tableComboBoxModel.removeAllElements();
                            tableComboBoxModel.addAll(allTable);
                            tableComboBox.setSelectedIndex(-1);
                        });
                        List<String> tableValueList = tableComboBoxKeyListener.getList();
                        tableValueList.clear();
                        tableValueList.addAll(allTable);
                        logPanel.getLogTextArea().append(StrUtil.format(DateUtil.now() + " - " + "已获取 {} 下的 {} 下所有的表的数量为: {}\n", sinkKey, schema, allTable.size()));
                    }
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                         NoSuchMethodException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }, () -> {
                tableButton.setEnabled(true);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setVisible(false);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setBusy(false);
            }, logPanel.getLogTextArea()));

            clearTableButton.addActionListener(_ -> CompletableFutureUtil.runAndThrowExceptionFrontPresent(() -> {
                clearTableButton.setEnabled(false);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setVisible(true);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setBusy(true);

                SinkPanel sinkPanel = taskConfigPanel.getSinkPanel();
                JComboBox<String> sinkComboBox = sinkPanel.getSinkComboBox();
                String sinkText = sinkComboBox.getItemAt(sinkComboBox.getSelectedIndex());
                Assert.isTrue(StrUtil.isNotBlank(sinkText), "请先选择 Sink");

                Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                Assert.isTrue(connectionInfoMap.containsKey(sinkText), "根据 key 找不到 Sink : " + sinkText);
                JdbcConnectionInfo jdbcConnectionInfo = ConfigFileUtil.CONNECTION_INFO_MAP.get(sinkText);
                String schemaText = sinkPanel.getSchemaTextField().getText();
                String tableText = sinkPanel.getTableTextField().getText();
                Assert.isTrue(StrUtil.isNotBlank(schemaText), "请先选择 Schema");
                Assert.isTrue(StrUtil.isNotBlank(tableText), "请先选择 Table");

                int confirm = JOptionPane.showConfirmDialog(
                        Main.rootFrame,
                        "是否确认清空 " + schemaText + "." + tableText,
                        "确认",
                        JOptionPane.YES_NO_OPTION
                );
                if (confirm == JOptionPane.YES_OPTION) {
                    String deleteSql = StrUtil.format("delete from {}{}{}.{}{}{}", jdbcConnectionInfo.getIdentifier(), schemaText, jdbcConnectionInfo.getIdentifier(), jdbcConnectionInfo.getIdentifier(), tableText, jdbcConnectionInfo.getIdentifier());
                    log.info("delete sql: {}", deleteSql);
                    logPanel.getLogTextArea().append(DateUtil.now() + " - " + "delete sql: " + deleteSql + "\n");

                    DataSource dataSource = JdbcConnectionUtil.getDataSource(jdbcConnectionInfo);
                    try (Connection connection = dataSource.getConnection();
                         PreparedStatement preparedStatement = connection.prepareStatement(deleteSql)) {
                        int deleteRowCount = preparedStatement.executeUpdate();
                        log.info("删除条数: {}", deleteRowCount);
                        logPanel.getLogTextArea().append(DateUtil.now() + " - " + "删除条数: " + deleteRowCount + "\n");
                    } catch (SQLException ex) {
                        throw new RuntimeException(ex.getMessage(), ex);
                    }
                }
            }, () -> {
                clearTableButton.setEnabled(true);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setVisible(false);
                taskConfigPanel.getRunButtonPanel().getJxBusyLabel().setBusy(false);
            }, logPanel.getLogTextArea()));

            sinkConfigButton.addActionListener(_ -> {
                sinkConfigDialog.setLocationRelativeTo(Main.rootFrame);
                sinkConfigDialog.setVisible(true);
            });
        }
    }

    @Getter
    public static class SinkConfigDialog extends JDialog {
        private static final int WIDTH = 500;
        private static final int HEIGHT = 500;

        private JComboBox<Integer> threadComboBox;
        private JTextField batchSizeTextField;

        public SinkConfigDialog() {
            super(Main.rootFrame, "Sink Config", true);
            setSize(WIDTH, HEIGHT);

            setLayout(new MigLayout("insets 20 20 20 20, fillx", "[]", "[] [] [] [] []"));
            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

            initUI();
            initUIData();
            initAction();

            addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    // 校验
                    String batchSizeText = batchSizeTextField.getText();
                    if (!StrUtil.isNotBlank(batchSizeText) || !NumberUtil.isInteger(batchSizeText) || Integer.parseInt(batchSizeText) <= 0) {
                        String errMsg = "批次量必须为正整数";
                        JOptionPane.showMessageDialog(
                                Main.rootFrame,
                                errMsg,
                                "异常",
                                JOptionPane.ERROR_MESSAGE
                        );
                        throw new RuntimeException(errMsg);
                    }

                    super.windowClosing(e);
                }
            });
        }

        private void initUI() {
            // 线程数
            JLabel threadLabel = new JLabel("线程数:");
            add(threadLabel, "cell 0 0, wmin 60, wmax 60");

            Integer[] threadArr = new Integer[20];
            for (int i = 1; i <= threadArr.length; i++) {
                threadArr[i - 1] = i;
            }
            DefaultComboBoxModel<Integer> threadComboBoxModel = new DefaultComboBoxModel<>(threadArr);
            threadComboBox = new JComboBox<>(threadComboBoxModel);
            add(threadComboBox, "cell 0 0, growx, gapx 10");

            // 批次量
            JLabel batchSizeLabel = new JLabel("批次量:");
            add(batchSizeLabel, "cell 0 1, wmin 60, wmax 60, gapy 20");

            batchSizeTextField = new JTextField("1000");
            batchSizeTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
            add(batchSizeTextField, "cell 0 1, growx, gapx 10");
        }

        private void initUIData() {

        }

        private void initAction() {

        }
    }

    @Getter
    public class RunButtonPanel extends JPanel {
        private JSplitButton insertButton;
        private JMenuItem upsertButton;
        private JButton cancelButton;
        public JXBusyLabel jxBusyLabel;

        public RunButtonPanel() {
            MigLayout migLayout = new MigLayout("insets 10 10 10 10, fillx", "[] []", "[]");
            setLayout(migLayout);
            initUI();
            initAction();
        }

        private void initUI() {
            jxBusyLabel = new JXBusyLabel();
            jxBusyLabel.setBusy(false);
            jxBusyLabel.setDelay(100); // 动画延迟时间
            jxBusyLabel.setVisible(false);
            add(jxBusyLabel, "cell 0 0, aligny bottom");

            cancelButton = new JButton("cancel");
            cancelButton.setVisible(false);
            add(cancelButton, "cell 0 0, aligny bottom");

            insertButton = new JSplitButton("Insert");
            JPopupMenu jPopupMenu = new JPopupMenu();
            insertButton.setPopupMenu(jPopupMenu);
            upsertButton = new JMenuItem("Upsert");
            jPopupMenu.add(upsertButton);

            add(insertButton, "cell 1 0, aligny bottom, alignx right");
        }

        private void initAction() {
            insertButton.addButtonClickedActionListener(_ -> CompletableFutureUtil.runAndThrowExceptionFrontPresent(() -> {
                insertButton.setEnabled(false);
                jxBusyLabel.setVisible(true);
                jxBusyLabel.setBusy(true);
                cancelButton.setVisible(true);

                // sourcePanel
                SourcePanel sourcePanel = taskConfigPanel.getSourcePanel();
                JComboBox<String> sourceComboBox = sourcePanel.getSourceComboBox();
                String sourceText = sourceComboBox.getItemAt(sourceComboBox.getSelectedIndex());
                Assert.isTrue(StrUtil.isNotBlank(sourceText), "请先选择 Source");

                BaseSourceConfig baseSourceConfig;
                if (StrUtil.equals(sourceText, "File")) {
                    // 文件source
                    // todo 配置需要填充
                    String filePathText = sourcePanel.getFilePathTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(filePathText), "请先填写 source 的文件路径");
                    if (StrUtil.endWithIgnoreCase(filePathText, ".csv")) {
                        baseSourceConfig = new CsvSourceConfig(filePathText);
                    } else if (StrUtil.endWithIgnoreCase(filePathText, ".xlsx")) {
                        baseSourceConfig = new ExcelSourceConfig(filePathText);
                    } else {
                        baseSourceConfig = new TextSourceConfig(filePathText);
                    }
                } else {
                    // jdbc source
                    Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                    Assert.isTrue(connectionInfoMap.containsKey(sourceText), "根据 key 找不到 Source : " + sourceText);
                    JdbcConnectionInfo jdbcConnectionInfo = ConfigFileUtil.CONNECTION_INFO_MAP.get(sourceText);
                    String sqlText = sourcePanel.getSqlTextArea().getText();
                    Assert.isTrue(StrUtil.isNotBlank(sqlText), "请先填写 Sql");

                    // todo fetchSize 根据不同数据库设置不同值
                    if (StrUtil.equalsIgnoreCase(jdbcConnectionInfo.getDbType(), "mysql")) {
                        baseSourceConfig = new JdbcSourceConfig(jdbcConnectionInfo, sqlText, Integer.MIN_VALUE);
                    } else {
                        baseSourceConfig = new JdbcSourceConfig(jdbcConnectionInfo, sqlText, 1000);
                    }
                }

                // sinkPanel
                SinkPanel sinkPanel = taskConfigPanel.getSinkPanel();
                JComboBox<String> sinkComboBox = sinkPanel.getSinkComboBox();
                String sinkText = sinkComboBox.getItemAt(sinkComboBox.getSelectedIndex());
                Assert.isTrue(StrUtil.isNotBlank(sinkText), "请先选择 Sink");

                BaseSinkConfig baseSinkConfig;
                if (StrUtil.equals(sinkText, "File")) {
                    String filePathText = sinkPanel.getFilePathTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(filePathText), "请先填写 sink 的文件路径");
                    if (StrUtil.endWithIgnoreCase(filePathText, ".csv")) {
                        baseSinkConfig = new CsvSinkConfig(filePathText);
                    } else if (StrUtil.endWithIgnoreCase(filePathText, ".xlsx")) {
                        baseSinkConfig = new ExcelSinkConfig(filePathText);
                    } else {
                        baseSinkConfig = new TextSinkConfig(filePathText);
                    }
                } else {
                    Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                    Assert.isTrue(connectionInfoMap.containsKey(sinkText), "根据 key 找不到 Sink : " + sinkText);
                    JdbcConnectionInfo jdbcConnectionInfo = ConfigFileUtil.CONNECTION_INFO_MAP.get(sinkText);
                    String schemaText = sinkPanel.getSchemaTextField().getText();
                    String tableText = sinkPanel.getTableTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(schemaText), "请先选择 Schema");
                    Assert.isTrue(StrUtil.isNotBlank(tableText), "请先选择 Table");

                    baseSinkConfig = new JdbcSinkConfig(jdbcConnectionInfo, schemaText, tableText);
                }

                SinkConfigDialog sinkConfigDialog = sinkPanel.getSinkConfigDialog();
                JComboBox<Integer> threadComboBox = sinkConfigDialog.getThreadComboBox();
                Integer threadCount = threadComboBox.getItemAt(threadComboBox.getSelectedIndex());
                baseSinkConfig.setThreadCount(threadCount);

                JTextField batchSizeTextField = sinkConfigDialog.getBatchSizeTextField();
                baseSinkConfig.setBatchSize(Integer.parseInt(batchSizeTextField.getText()));

                // transformPanel
                TransformPanel transformPanel = taskConfigPanel.getTransformPanel();
                JCheckBox mockCheckBox = transformPanel.getMockCheckBox();

                List<StsTransform<?>> transformList = new ArrayList<>();
                if (mockCheckBox.isSelected()) {
                    String symbolText = transformPanel.getSymbolTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(symbolText), "transform 符号不能为空");
                    RuleGenerateTransformImpl ruleGenerateTransform = new RuleGenerateTransformImpl(symbolText);
                    transformList.add(ruleGenerateTransform);
                }

                stsTask = new StsTask(baseSourceConfig, baseSinkConfig, SinkTypeEnum.INSERT, transformList);
                stsTask.setLogTextArea(logPanel.getLogTextArea());
                stsTask.run();
            }, () -> {
                insertButton.setEnabled(true);
                jxBusyLabel.setVisible(false);
                jxBusyLabel.setBusy(false);
                cancelButton.setVisible(false);
            }, logPanel.getLogTextArea()));

            upsertButton.addActionListener(_ -> CompletableFutureUtil.runAndThrowExceptionFrontPresent(() -> {
                insertButton.setEnabled(false);
                jxBusyLabel.setVisible(true);
                jxBusyLabel.setBusy(true);
                cancelButton.setVisible(true);

                // sourcePanel
                SourcePanel sourcePanel = taskConfigPanel.getSourcePanel();
                JComboBox<String> sourceComboBox = sourcePanel.getSourceComboBox();
                String sourceText = sourceComboBox.getItemAt(sourceComboBox.getSelectedIndex());
                Assert.isTrue(StrUtil.isNotBlank(sourceText), "请先选择 Source");

                BaseSourceConfig baseSourceConfig;
                if (StrUtil.equals(sourceText, "File")) {
                    // 文件source
                    // todo 配置需要填充
                    String filePathText = sourcePanel.getFilePathTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(filePathText), "请先填写 source 的文件路径");
                    if (StrUtil.endWithIgnoreCase(filePathText, ".csv")) {
                        baseSourceConfig = new CsvSourceConfig(filePathText);
                    } else if (StrUtil.endWithIgnoreCase(filePathText, ".xlsx")) {
                        baseSourceConfig = new ExcelSourceConfig(filePathText);
                    } else {
                        baseSourceConfig = new TextSourceConfig(filePathText);
                    }
                } else {
                    // jdbc source
                    Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                    Assert.isTrue(connectionInfoMap.containsKey(sourceText), "根据 key 找不到 Source : " + sourceText);
                    JdbcConnectionInfo jdbcConnectionInfo = ConfigFileUtil.CONNECTION_INFO_MAP.get(sourceText);
                    String sqlText = sourcePanel.getSqlTextArea().getText();
                    Assert.isTrue(StrUtil.isNotBlank(sqlText), "请先填写 Sql");

                    // todo fetchSize 根据不同数据库设置不同值
                    if (StrUtil.equalsIgnoreCase(jdbcConnectionInfo.getDbType(), "mysql")) {
                        baseSourceConfig = new JdbcSourceConfig(jdbcConnectionInfo, sqlText, Integer.MIN_VALUE);
                    } else {
                        baseSourceConfig = new JdbcSourceConfig(jdbcConnectionInfo, sqlText, 1000);
                    }
                }

                // sinkPanel
                SinkPanel sinkPanel = taskConfigPanel.getSinkPanel();
                JComboBox<String> sinkComboBox = sinkPanel.getSinkComboBox();
                String sinkText = sinkComboBox.getItemAt(sinkComboBox.getSelectedIndex());
                Assert.isTrue(StrUtil.isNotBlank(sinkText), "请先选择 Sink");

                BaseSinkConfig baseSinkConfig;
                if (StrUtil.equals(sinkText, "File")) {
                    String filePathText = sinkPanel.getFilePathTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(filePathText), "请先填写 sink 的文件路径");
                    if (StrUtil.endWithIgnoreCase(filePathText, ".csv")) {
                        baseSinkConfig = new CsvSinkConfig(filePathText);
                    } else if (StrUtil.endWithIgnoreCase(filePathText, ".xlsx")) {
                        baseSinkConfig = new ExcelSinkConfig(filePathText);
                    } else {
                        baseSinkConfig = new TextSinkConfig(filePathText);
                    }
                } else {
                    Map<String, JdbcConnectionInfo> connectionInfoMap = ConfigFileUtil.CONNECTION_INFO_MAP;
                    Assert.isTrue(connectionInfoMap.containsKey(sinkText), "根据 key 找不到 Sink : " + sinkText);
                    JdbcConnectionInfo jdbcConnectionInfo = ConfigFileUtil.CONNECTION_INFO_MAP.get(sinkText);
                    String schemaText = sinkPanel.getSchemaTextField().getText();
                    String tableText = sinkPanel.getTableTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(schemaText), "请先选择 Schema");
                    Assert.isTrue(StrUtil.isNotBlank(tableText), "请先选择 Table");

                    baseSinkConfig = new JdbcSinkConfig(jdbcConnectionInfo, schemaText, tableText);
                }

                SinkConfigDialog sinkConfigDialog = sinkPanel.getSinkConfigDialog();
                JComboBox<Integer> threadComboBox = sinkConfigDialog.getThreadComboBox();
                Integer threadCount = threadComboBox.getItemAt(threadComboBox.getSelectedIndex());
                baseSinkConfig.setThreadCount(threadCount);

                JTextField batchSizeTextField = sinkConfigDialog.getBatchSizeTextField();
                baseSinkConfig.setBatchSize(Integer.parseInt(batchSizeTextField.getText()));

                // transformPanel
                TransformPanel transformPanel = taskConfigPanel.getTransformPanel();
                JCheckBox mockCheckBox = transformPanel.getMockCheckBox();

                List<StsTransform<?>> transformList = new ArrayList<>();
                if (mockCheckBox.isSelected()) {
                    String symbolText = transformPanel.getSymbolTextField().getText();
                    Assert.isTrue(StrUtil.isNotBlank(symbolText), "transform 符号不能为空");
                    RuleGenerateTransformImpl ruleGenerateTransform = new RuleGenerateTransformImpl(symbolText);
                    transformList.add(ruleGenerateTransform);
                }

                stsTask = new StsTask(baseSourceConfig, baseSinkConfig, SinkTypeEnum.UPSERT, transformList);
                stsTask.setLogTextArea(logPanel.getLogTextArea());
                stsTask.run();
            }, () -> {
                insertButton.setEnabled(true);
                jxBusyLabel.setVisible(false);
                jxBusyLabel.setBusy(false);
                cancelButton.setVisible(false);
            }, logPanel.getLogTextArea()));

            cancelButton.addActionListener(_ -> {
                if (stsTask != null) {
                    stsTask.setFlag(false);
                }
            });
        }
    }

}
